

#pragma once

#include <iostream>

#include <fstream>
#include <sstream>

#ifdef __APPLE__
#include <OpenCL/cl.h>
#else

#include <CL/cl.h>

#endif

namespace ocl {
    class cl_base {
    public:
        cl_base() : mbase_inited(false) {
            if (init() == 0) {
                mbase_inited = true;
            }
        }

        ~cl_base() {}

        static cl_context createContext();

        static cl_command_queue createCommandQueue(cl_context context, cl_device_id *device);

        static cl_program createProgram(cl_context context, cl_device_id device, const char *fileName);

        int32_t init();

    public:
        cl_context mcontext;
        cl_command_queue mcommandQueue;
        cl_device_id mdeviceid;
        bool mbase_inited;
    };



    static const int ARRAY_SIZE = 1000;
    class cl_affair : cl_base {
    public:
        cl_affair(std::string clfile, std::string kernelname) {
            mclfile = clfile;
            mkernelname = kernelname;
        }

        ~cl_affair() {}

        bool CreateMemObjects(cl_context context, cl_mem memObjects[3],
                              float *a, float *b)
        {
            memObjects[0] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
                                           sizeof(float) * ARRAY_SIZE, a, NULL);
            memObjects[1] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
                                           sizeof(float) * ARRAY_SIZE, b, NULL);
            memObjects[2] = clCreateBuffer(context, CL_MEM_READ_WRITE,
                                           sizeof(float) * ARRAY_SIZE, NULL, NULL);

            if (memObjects[0] == NULL || memObjects[1] == NULL || memObjects[2] == NULL) {
                std::cerr << "Error creating memory objects." << std::endl;
                return false;
            }

            return true;
        }
        int32_t init() {
            if (mbase_inited) {
                mprogram = cl_base::createProgram(mcontext, mdeviceid, mclfile.c_str());
            } else {
                std::cout << "please init cl base first" << std::endl;
                return -1;
            }
            mkernel = clCreateKernel(mprogram, mkernelname.c_str(), NULL);
            if (mkernel == NULL) {
                std::cerr << "Failed to create kernel" << std::endl;
                return -2;
            }

            float a[ARRAY_SIZE];
            float b[ARRAY_SIZE];
            for (int i = 0; i < ARRAY_SIZE; i++) {
                a[i] = (float) i;
                b[i] = (float) (i * 2);
            }
            CreateMemObjects(mcontext,mmemObjects,a,b);
            maffair_inited = true;
            return 0;
        }
        int32_t attach()
        {
            // Set the kernel arguments (result, a, b)
            cl_int errNum;
            errNum = clSetKernelArg(mkernel, 0, sizeof(cl_mem), &mmemObjects[0]);
            errNum |= clSetKernelArg(mkernel, 1, sizeof(cl_mem), &mmemObjects[1]);
            errNum |= clSetKernelArg(mkernel, 2, sizeof(cl_mem), &mmemObjects[2]);
            if (errNum != CL_SUCCESS) {
                std::cerr << "Error setting kernel arguments." << std::endl;
                return 1;
            }
            return 0;
        }
        int32_t run()
        {
            float result[ARRAY_SIZE];
            cl_int errNum;
            size_t globalWorkSize[1] = {ARRAY_SIZE};
            size_t localWorkSize[1] = {1};

            // Queue the kernel up for execution across the array
            errNum = clEnqueueNDRangeKernel(mcommandQueue, mkernel, 1, NULL,
                                            globalWorkSize, localWorkSize,
                                            0, NULL, NULL);
            if (errNum != CL_SUCCESS) {
                std::cerr << "Error queuing kernel for execution." << std::endl;
                return 1;
            }

            // Read the output buffer back to the Host
            errNum = clEnqueueReadBuffer(mcommandQueue, mmemObjects[2], CL_TRUE,
                                         0, ARRAY_SIZE * sizeof(float), result,
                                         0, NULL, NULL);
            if (errNum != CL_SUCCESS) {
                std::cerr << "Error reading result buffer." << std::endl;
                return 1;
            }

            // Output the result buffer
            for (int i = 0; i < ARRAY_SIZE; i++) {
                std::cout << result[i] << " ";
            }
            std::cout << std::endl;
            std::cout << "Executed program succesfully." << std::endl;
        }

    public:
        cl_program mprogram;
        cl_kernel mkernel;
        cl_mem mmemObjects[3] = {0, 0, 0};

        std::string mclfile;
        std::string mkernelname;
        bool maffair_inited;
    };

    class wrapper {
    public:
        wrapper() {}

        ~wrapper() {}

        static cl_context CreateContext();

        static cl_command_queue CreateCommandQueue(cl_context context, cl_device_id *device);

        static cl_program CreateProgram(cl_context context, cl_device_id device, const char *fileName);

        static bool CreateMemObjects(cl_context context, cl_mem memObjects[3],
                                     float *a, float *b);

        static void Cleanup(cl_context context, cl_command_queue commandQueue,
                            cl_program program, cl_kernel kernel, cl_mem memObjects[3]);


    };
}


